package com.tangruojie;

import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.*;

/**
 * Logger 是 Java 提供的日志记录工具类，属于 java.util.logging 包，是 JDK 自带的日志框架（又叫 JUL，全称 Java Util Logging）。
 * 它用于在应用程序中记录调试信息、错误信息、运行状态等日志内容。
 * 默认日志级别是 INFO，只会打印 INFO 以上（即 INFO, WARNING, SEVERE）；
 * FINE, FINER, FINEST 默认不会打印，需要手动开启。
 */

/**
 * 在 java.util.logging.Level 中，日志级别从高到低的顺序是：
 * 等级	            描述
 * SEVERE	        严重错误
 * WARNING	        警告
 * INFO	            信息（默认起始级别）
 * CONFIG	        配置信息
 * FINE	            一般调试信息
 * FINER	        更详细的调试信息
 * FINEST	        最详细的调试信息
 * ALL	            打印所有级别（最低）
 * OFF	            关闭所有日志（最高）
 * 只有当 logger 本身 和 Handler（处理器） 都设置为 Level.ALL 或足够低，才能看到所有输出。
 * 设置 logger.setLevel(Level.ALL) 不等于所有日志都会输出，因为还要看关联的 Handler（如 ConsoleHandler）是否允许低等级日志输出！
 */
public class JULTest {

    /**
     * 快速入门
     */
    @Test
    public void testQuick() {
        /**
         * 1、获取日志记录对象
         */
        Logger logger = Logger.getLogger(JULTest.class.getName());
        /**
         * 2、日志记录输出
         */
        logger.info("hello jul");
        /**
         * 通用方法进行日志记录
         */
        logger.log(Level.INFO, "info msg");
        /**
         * 通过占位符方式输出变量值
         */
        String name = "tangruojie";
        int age = 18;
        logger.log(Level.INFO, "name: {0}, age: {1}", new Object[]{name, age});
    }

    /**
     * 日志级别
     */
    @Test
    public void testLogLevel() {
        /**
         * 1、获取日志记录对象
         */
        Logger logger = Logger.getLogger(JULTest.class.getName());
        /**
         * 2、日志记录输出
         */
        logger.severe("severe msg");
        logger.warning("warning msg");
        logger.info("info msg");
        /**
         * 默认日志级别是 INFO，只会打印 INFO 以上（即 INFO, WARNING, SEVERE）
         * CONFIG, FINE, FINER, FINEST 默认不会打印，需要手动开启
         */
        logger.config("config msg");
        logger.fine("fine msg");
        logger.finer("finer msg");
        logger.finest("finest msg");
    }

    /**
     * 自定义日志级别
     * 一个 Logger 可以拥有多个 Handler
     * 一个 Handler 可以被多个 Logger 共用（虽然不常见）
     * 所以，Logger 与 Handler 是多对多的关系，但常见用法中我们通常是一个 Logger 搭配多个 Handler。
     */
    @Test
    public void testCustomLogLevel() {
        /**
         * 1、获取日志记录对象
         */
        Logger logger = Logger.getLogger(JULTest.class.getName());
        /**
         * 关闭系统默认日志级别
         */
        logger.setUseParentHandlers(false);
        /**
         * 自定义配置日志级别
         */
        /**
         * 创建控制台处理器 ConsoleHandler 输出日志到控制台
         */
        ConsoleHandler consoleHandler = new ConsoleHandler();
        /**
         * 创建简单格式转化对象 SimpleFormatter
         */
        SimpleFormatter simpleFormatter = new SimpleFormatter();
        /**
         * 设置控制台处理器的格式转化对象
         */
        consoleHandler.setFormatter(simpleFormatter);
        /**
         * 添加控制台处理器
         */
        logger.addHandler(consoleHandler);
        /**
         * 设置日志具体级别
         * 只有当 logger 本身 和 Handler（处理器） 都设置为 Level.ALL 或足够低，才能看到所有输出。
         * 设置 logger.setLevel(Level.ALL) 不等于所有日志都会输出，因为还要看关联的 Handler（如 ConsoleHandler）是否允许低等级日志输出！
         */
        consoleHandler.setLevel(Level.ALL);
        logger.setLevel(Level.ALL);
        /**
         * 一个 Logger 可以添加多个 Handler（处理器），一个 Handler 可以处理多个 Logger
         */
        /**
         * 创建文件处理器 FileHandler 输出日志到文件
         */
        try {
            /**
             * FileHandler 不会自动创建目录
             */
            File logDir = new File("logs");
            if (!logDir.exists()) {
                logDir.mkdirs(); // 自动创建 logs 目录
            }
            FileHandler fileHandler = new FileHandler("logs/jul.log");
            fileHandler.setFormatter(simpleFormatter);
            logger.addHandler(fileHandler);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        /**
         * 2、日志记录输出
         */
        logger.severe("severe msg");
        logger.warning("warning msg");
        logger.info("info msg");
        logger.config("config msg");
        logger.fine("fine msg");
        logger.finer("finer msg");
        logger.finest("finest msg");
    }

    /**
     * Logger 对象的父子关系
     * Java 的 Logger 是基于 命名空间 构建父子关系的。
     * 子 Logger 会继承父 Logger 的处理器（Handler）和级别设置（Level），除非它自己显式配置了它们。
     */
    @Test
    public void testParentLogger() {
        /**
         * 子
         */
        Logger loggerSon = Logger.getLogger("com.tangruojie");
        /**
         * 父
         */
        Logger loggerFather = Logger.getLogger("com");
        /**
         * 判断父子关系
         */
        Logger parent = loggerSon.getParent();
        System.out.println(parent == loggerFather);
        /**
         * RootLogger 是 LogManager 的默认根日志记录器
         */
        System.out.println(loggerFather.getParent());
        /**
         * 关闭 loggerFather 的默认日志级别
         */
        loggerFather.setUseParentHandlers(false);
        /**
         * 设置 loggerFather 的日志级别
         */
        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setFormatter(new SimpleFormatter());
        loggerFather.addHandler(consoleHandler);
        consoleHandler.setLevel(Level.ALL);
        loggerFather.setLevel(Level.ALL);
        /**
         * 输出日志
         * 子继承父
         */
        loggerSon.severe("severe msg");
        loggerSon.warning("warning msg");
        loggerSon.info("info msg");
        loggerSon.config("config msg");
        loggerSon.fine("fine msg");
        loggerSon.finer("finer msg");
        loggerSon.finest("finest msg");
    }

    /**
     * 加载自定义配置文件
     * 默认情况下，JUL（java.util.logging）并不会自动加载你放在 resources 文件夹下的 logging.properties 配置文件，除非你明确地通过某种方式告知 JVM 去加载它。
     * ------
     * 如果你没有调用 LogManager.readConfiguration(...)，也没有通过 -D 启动参数设置配置文件，则：
     * Logger 会使用 JDK 默认配置（在 $JAVA_HOME/jre/lib/logging.properties 中定义）：
     * 默认级别是 INFO
     * 使用 ConsoleHandler
     * fine、finer、finest 等默认不会输出
     * ------
     * 可以使用任何类的 .class.getClassLoader() 来获取类加载器，只要该类是通过 应用类加载器（Application ClassLoader） 加载的，并且你要找的文件 logging.properties 能通过这个类加载器在类路径（classpath）中被访问到。
     */
    @Test
    public void testCustomConfigFile() throws IOException {
        /**
         * 1、读取配置文件（通过类加载器）
         * logging.properties 是标准 JUL 的配置文件，里面可以配置日志级别、格式、Handler（输出到控制台/文件）等
         * ------
         * 1、getResourceAsStream(...) 是相对于 类路径（classpath） 来查找文件的；
         * 所以 logging.properties 应该放在 src/main/resources/，构建后会在 target/classes/ 中。
         * 2、使用哪个类的 .getClassLoader()，其实只是一个找路径的入口，只要它能访问到你的资源就行。
         * 3、如果你用的是 xxx.class.getResourceAsStream(...)（不是 getClassLoader()）：
         * 路径处理有差异，比如是否以 / 开头会改变查找方式。
         */
        InputStream resource = JULTest.class.getClassLoader().getResourceAsStream("logging.properties");
        /**
         * 2、创建 LogManager
         * LogManager 是 JUL 的日志管理类，负责全局配置与日志系统管理。
         * getLogManager() 方法返回的是全局唯一的 LogManager 实例（单例）。
         */
        LogManager logManager = LogManager.getLogManager();
        /**
         * 3、加载配置文件
         * 将刚才读取的 logging.properties 文件内容加载到 LogManager 中。
         * 加载后，全局的日志行为（比如日志等级、输出格式等）都会受到这个配置的控制。
         */
        logManager.readConfiguration(resource);
        /**
         * 创建日志记录对象
         * 获取一个名为 JULTest.class.getName() 的 Logger 实例。
         * 如果已存在该名称的 Logger，就复用，否则新建。
         * Logger 是 JUL 的核心类，负责实际记录日志。
         */
        Logger logger = Logger.getLogger(JULTest.class.getName());
        /**
         * 输出日志
         */
        logger.severe("severe msg");
        logger.warning("warning msg");
        logger.info("info msg");
        logger.config("config msg");
        logger.fine("fine msg");
        logger.finer("finer msg");
        logger.finest("finest msg");

        Logger loggerTest = Logger.getLogger("test");
        /**
         * 输出日志
         */
        loggerTest.severe("severe test");
        loggerTest.warning("warning test");
        loggerTest.info("info test");
        loggerTest.config("config test");
        loggerTest.fine("fine test");
        loggerTest.finer("finer test");
        loggerTest.finest("finest test");
    }

}
/**
 * 和其他日志框架的比较
 * 框架	                        特点
 * java.util.logging (JUL)	    JDK 自带，零依赖，配置复杂
 * Log4j	                    强大，可配置性好，经典框架
 * SLF4J	                    门面接口，和 Logback、Log4j 搭配使用
 * Logback	                    Spring Boot 默认使用的日志框架，功能强大
 * ------
 * 和其他框架对比（如 Log4j、SLF4J）
 * JUL (Level)	    Log4j / SLF4J	说明
 * SEVERE	        ERROR	        致命错误
 * WARNING	        WARN	        警告
 * INFO	            INFO	        正常输出
 * FINE	            DEBUG	        调试
 * FINER	        TRACE	        更详细调试
 * ALL	            ALL	            全部输出
 * ------
 * 为什么用 Logger 而不是 System.out.println？
 * System.out.println	        Logger
 * 无法控制日志级别	            支持多级日志控制
 * 无法控制输出位置	            可写入文件、数据库
 * 不支持格式化输出	            支持格式化输出
 * 不适合生产环境	                推荐用于开发调试
 * ------
 * LogManager 在整个 JUL 系统中的作用
 * 类	        作用
 * LogManager	管理所有 Logger 的注册、配置、生命周期
 * Logger	    记录日志的对象，由 LogManager 创建并缓存
 * Handler	    日志输出器，如 ConsoleHandler、FileHandler
 * Formatter	输出格式器，如 SimpleFormatter、XMLFormatter
 * ------
 * JUL 的日志系统遵循层级命名规则（logger hierarchy）：
 * 每个 Logger 有一个名字（通常是包名或类名）。
 * Logger 会默认继承父 Logger 的配置（包括日志级别、Handler 等）。
 * 你可以在配置文件中通过指定 Logger 的名字，来为它单独配置行为。
 * # 为该 Logger 指定专属的 Handler（可选）
 * com.example.MyClass.handlers=java.util.logging.ConsoleHandler
 * ------
 * 如果你没有为某个 Logger 显式设置级别，它会向上查找父 Logger 的级别（比如 com.example，再到 com）。
 * 如果你没有设置专属 Handler，它会使用父级 Handler。
 * 如果你设置了 handlers=...，那么你必须自己显式设置 Handler，否则默认的控制台输出会失效。
 */
